import {
	ExecutionContext,
	Injectable,
	UnauthorizedException,
	SetMetadata,
	Inject,
	Logger
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { JwtService } from "@nestjs/jwt";
import { AuthGuard } from "@nestjs/passport";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { Cache } from "cache-manager";
import { ConfigService } from "@nestjs/config";
import { Users } from "../users/entities/user.entity";

// 注入依赖
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
	private logger = new Logger();
	constructor(
		private reflector: Reflector,
		private jwtService: JwtService,
		private readonly configService: ConfigService,
		@Inject(CACHE_MANAGER)
		private cacheManager: Cache
	) {
		super();
	}
	// 验证用户是否具有访问权限
	async canActivate(context: ExecutionContext) {
		// 获取请求上下文中的处理程序和类
		const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
			context.getHandler(),
			context.getClass()
		]);
		// 如果处理程序或类上有IS_PUBLIC_KEY装饰器，则跳过验证
		if (isPublic) {
			return true;
		}
		// 获取请求对象
		const request = context.switchToHttp().getRequest();
		// 获取请求头中的authorization字段
		const authorization = request["headers"].authorization || void 0;
		let tokenNotTimeOut = true;
		// 如果请求头中有authorization字段
		if (authorization) {
			// 获取token
			const token = authorization.split(" ")[1]; // authorization: Bearer xxx
			try {
				// 解码token，获取用户信息
				const user: Partial<Users> = this.jwtService.decode(token);
				// 从缓存中获取token
				const redis_token = await this.cacheManager.get(
					`${this.configService.get("JWT_SHORT_KEY")}:${user.id}`
				);
				// 如果缓存中没有token或者缓存中的token与请求中的token不一致，则抛出异常
				if (!redis_token || redis_token !== token) {
					const redis_long_token = await this.cacheManager.get(
						`${this.configService.get("JWT_LONG_KEY")}:${user.id}`
					);
					if (!redis_long_token) {
						throw 4001;
					} else {
						throw 4008;
					}
				}
			} catch (err) {
				// 如果解码token失败，则设置tokenNotTimeOut为false
				tokenNotTimeOut = false;
				switch (err) {
					case 4001:
						throw new UnauthorizedException("请重新登录");
					case 4008:
						throw new UnauthorizedException("请获取新的token", {
							cause: 4008,
							description: "token过期,更换token"
						});
					default:
						throw new UnauthorizedException("请重新登录");
				}
			}
		} else {
			throw new UnauthorizedException("请登录");
		}
		// 返回tokenNotTimeOut和父类验证结果
		return tokenNotTimeOut && (super.canActivate(context) as boolean);
		// return super.canActivate(context);
	}

	// 处理请求
	handleRequest(err, user, info) {
		// You can throw an exception based on either "info" or "err" arguments
		if (info) {
			this.logger.log("Received additional information:", err);
		}
		if (err || !user) {
			throw err || new UnauthorizedException();
		}
		return user;
	}
}
@Injectable()
export class RefreshJwtAuthGuard extends AuthGuard("refreshJwt") {
	private logger = new Logger();
	constructor(
		private reflector: Reflector,
		private jwtService: JwtService,
		private readonly configService: ConfigService,
		@Inject(CACHE_MANAGER)
		private cacheManager: Cache
	) {
		super();
	}
	async canActivate(context: ExecutionContext) {
		const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
			context.getHandler(),
			context.getClass()
		]);
		if (isPublic) {
			return true;
		}
		const request = context.switchToHttp().getRequest();
		const authorization = request["headers"].authorization || void 0;
		let tokenNotTimeOut = true;
		if (authorization) {
			const token = authorization.split(" ")[1]; // authorization: Bearer xxx
			try {
				const user: Partial<Users> = this.jwtService.decode(token);
				const redis_token = await this.cacheManager.get(
					`${this.configService.get("JWT_LONG_KEY")}:${user.id}`
				);
				if (!redis_token || redis_token !== token) {
					throw new UnauthorizedException("请重新登录");
				}
			} catch (err) {
				tokenNotTimeOut = false;
				throw new UnauthorizedException("请重新登录", err);
			}
		} else {
			throw new UnauthorizedException("请登录");
		}
		return tokenNotTimeOut && (super.canActivate(context) as boolean);
		// return super.canActivate(context);
	}

	handleRequest(err, user, info) {
		// You can throw an exception based on either "info" or "err" arguments
		if (info) {
			this.logger.log("Received additional information:", err);
		}
		if (err || !user) {
			throw err || new UnauthorizedException();
		}
		return user;
	}
}
// 生成跳过检测装饰器
export const IS_PUBLIC_KEY = "isPublic";
export const SkipAuth = () => SetMetadata(IS_PUBLIC_KEY, true);
